package model

import (
	j_log "config-client-v1/init/j-log"
	"config-client-v1/utils/tools"
	"context"
	"encoding/json"
	"fmt"
	"github.com/jacobsa/go-serial/serial"
	"github.com/olekukonko/tablewriter"
	"io"
	"os"
	"runtime/debug"
	"strings"
	"time"
)

type Rs485Config struct {
	PortName        string `json:"port_name"`         // 串口名称 COM1,COM2...
	BaudRate        uint   `json:"baud_rate"`         // 波特率 9600
	DataBits        uint   `json:"data_bits"`         // 数据位 8
	StopBits        uint   `json:"stop_bits"`         // 停止位 1
	MinimumReadSize uint   `json:"minimum_read_size"` // 最小读取字节 4
	ParityMode      int    `json:"parity_mode"`       // 奇偶检验 0.NONE 1.ODD 2.EVEN
	WorkFlow        int    `json:"work_flow"`         // 流控制 0.NONE 1.XON/XOFF 2.硬件控制
}

func (ppe *PPE) rs485FrameSendProcess(ctx context.Context, frame *Frame) {
	defer func() {
		if err := recover(); err != nil {
			j_log.LogError([]interface{}{"rs485FrameSendProcess panic", err})
			fmt.Println(string(debug.Stack()))
		}
		j_log.LogInfo([]interface{}{fmt.Sprintf("frame_id=%d, rs485FrameSendProcess 协程已关闭", frame.ID)})
	}()
	for {
		select {
		case <-ctx.Done():
			return
		default:
			if frame.SendType == 1 {
				list := make([]*FrameItemParam, 0)
				err := json.Unmarshal([]byte(frame.JsonDetail), &list)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, 发送帧json解析失败 frame.JsonDetail; %s", frame.ID, err.Error())})
					return
				}
				data, err := CreateFrameRawData(list)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, 发送帧生成帧原始数据失败; %s", frame.ID, err.Error())})
					return
				}
				port := *(ppe.RS485Client)
				_, err = port.Write(data)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("ppe_id=%d  串口写入失败:%s", ppe.ID, err.Error())})
					time.Sleep(time.Duration(frame.ScanRate) * time.Millisecond)
					continue
				}
				//j_log.LogInfo([]interface{}{fmt.Sprintf("【send】%s:ppe_id=%d, frame_id=%d, data = %v", ppe.ProtocolName, ppe.ID, frame.ID, data)})
				time.Sleep(time.Duration(frame.ScanRate) * time.Millisecond)
			} else if frame.SendType == 2 { // 事件触发
				match, err := IsTriggerConditionMatched(frame)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("[%s] frame_id=%d, IsTriggerConditionMatched error %s", frame.Name, frame.ID, err.Error())})
					return
				}
				if match {
					j_log.LogInfo([]interface{}{fmt.Sprintf("[%s] frame_id=%d, 事件触发帧满足条件", frame.Name, frame.ID)})
					if time.Now().Sub(frame.LastTriggerTime).Seconds() < float64(frame.TriggerInterval) {
						j_log.LogInfo([]interface{}{fmt.Sprintf("[%s] frame_id=%d, 事件触发帧距离下一次触发时间间隔未到, 跳过", frame.Name, frame.ID)})
						time.Sleep(1000 * time.Millisecond)
						continue
					}

					list := make([]*FrameItemParam, 0)
					err = json.Unmarshal([]byte(frame.JsonDetail), &list)
					if err != nil {
						j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, 发送帧json解析失败 frame.JsonDetail; %s", frame.ID, err.Error())})
						return
					}
					data, err := CreateFrameRawData(list)
					if err != nil {
						j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, 发送帧生成帧原始数据失败; %s", frame.ID, err.Error())})
						return
					}
					port := *(ppe.RS485Client)
					_, err = port.Write(data)
					if err != nil {
						j_log.LogError([]interface{}{fmt.Sprintf("ppe_id=%d  串口写入失败:%s", ppe.ID, err.Error())})
						time.Sleep(1000 * time.Millisecond)
						continue
					}

					frame.LastTriggerTime = time.Now()
				} else {
					j_log.LogInfo([]interface{}{fmt.Sprintf("[%s] frame_id=%d, 事件触发帧不满足条件, 跳过", frame.Name, frame.ID)})
				}
				time.Sleep(1000 * time.Millisecond)
			}
		}
	}
}

func (ppe *PPE) RunRS485() {
	defer func() {
		if err := recover(); err != nil {
			j_log.LogError([]interface{}{"RunRS485 panic", err})
			fmt.Println(string(debug.Stack()))
		}
		if ppe.RunningState == 3 {
			ppe.RunningState = 4 // 设备停止
			j_log.LogInfo([]interface{}{"【已停止】", ppe.ProtocolName, ppe.Name})
			_ = ppe.UpdateRunningState(4, "已停止")
		} else {
			ppe.RunningState = 0 // 设备异常
			_ = ppe.UpdateRunningState(0, "设备异常")
		}
		ppe.UnRegister()
	}()
	ppe.Register()
	config := Rs485Config{}
	err := json.Unmarshal([]byte(ppe.Config), &config)
	if err != nil {
		ppe.Detail = fmt.Sprintf("rs485_json_解析失败: %v %v %v", ppe.Name, config.PortName, err.Error())
		return
	}
	options := serial.OpenOptions{
		PortName:        config.PortName,
		BaudRate:        config.BaudRate,
		DataBits:        config.DataBits,
		StopBits:        config.StopBits,
		MinimumReadSize: config.MinimumReadSize,
		ParityMode:      serial.ParityMode(config.ParityMode),
	}
	port, err := serial.Open(options)
	if err != nil {
		j_log.LogError([]interface{}{"串口打开失败:", ppe.Name, config.PortName, err.Error()})
		ppe.Detail = fmt.Sprintf("串口打开失败: %v %v %v", ppe.Name, config.PortName, err.Error())
		return
	}
	j_log.LogInfo([]interface{}{fmt.Sprintf("【已启动】%s:%s, %s", ppe.ProtocolName, ppe.Name, config.PortName)})
	ppe.Detail = "已启动"
	_ = ppe.UpdateRunningState(2, "已启动")
	ppe.RunningState = 2 // 启动成功
	ppe.RS485Client = &port
	defer func() {
		if port != nil {
			_ = port.Close()
		}
	}()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	for _, frame := range ppe.FrameList {
		if frame.FrameType == "send" {
			if frame.SendType >= 1 && frame.SendType <= 2 {
				go ppe.rs485FrameSendProcess(ctx, frame)
			}
		}
	}
	go rs485WatchDog(ppe, port) // 看门狗监听设备是否收到停止信号
	// 同步处理接收帧
	for {
		buffer := make([]byte, 4096) // 缓冲区大小
		n, err2 := port.Read(buffer)
		if err2 != nil {
			if !strings.Contains(err2.Error(), "The I/O operation has been aborted") {
				j_log.LogError([]interface{}{"读取失败:" + err2.Error()})
			}
			return
		}
		now := tools.GetNow()
		// 输出读到的数据
		if n > 0 {
			time.Sleep(5 * time.Millisecond)
			rawData := tools.ByteArrayToHexStr(buffer[:n])
			//fmt.Printf("【%s】len=%d : %s\n", tools.GetNow(), n, recvData)
			j_log.LogInfo([]interface{}{"len=", n, rawData})
			frameMatched := false
			//  :::应该进行两轮 FrameList 循环, 第一轮匹配长度和功能码 , 第二轮再解析变量
		loop1:
			for _, frame := range ppe.FrameList {
				if !(frame.FrameType == "recv" && frame.Status == 1) {
					continue
				}
				if frame.Length != n {
					//j_log.LogError([]interface{}{"长度不匹配, 跳过此帧", frame.Length, n})
					continue loop1
				}
				/*--- 变量解析开始 ---*/
				frameItem := make([]*FrameItemParam, 0)
				err = json.Unmarshal([]byte(frame.JsonDetail), &frameItem)
				if err != nil {
					j_log.LogError([]interface{}{"json.Unmarshal ERROR frame.JsonDetail", err.Error()})
					return
				}

				data := make([][]string, 0)
				messageList := make([]*QueueValue, 0)
				for _, item := range frameItem {
					if item.InputMethod != 1 { // 如果不是变量就校验
						switch item.Type {
						case "包头":
						case "帧长":
						case "地址":
						case "功能码":
							//fmt.Println("匹配功能码")
							functionCode := tools.ByteArrayToHexStr(buffer[item.Start : item.End+1])
							if item.RawData != functionCode {
								j_log.LogError([]interface{}{"功能码不匹配!", item.RawData, functionCode})
								continue loop1
							}
						case "校验":
							if item.CheckMethod == "crc16" {
								checkRes := tools.CheckCRC16Custom(buffer, item.CheckStart, item.CheckEnd, item.Start, item.End)
								//fmt.Println("CRC16校验", checkRes)
								if !checkRes {
									j_log.LogError([]interface{}{"CRC16校验失败!", item.CheckStart, item.CheckEnd, item.Start, item.End})
									continue loop1
								} else {
									j_log.LogInfo([]interface{}{"crc16 校验成功"})
								}
							}
						case "帧尾":
							//fmt.Println("匹配帧尾")
						}
					} else { // 是变量则直接解析
						var value float64
						switch item.SpecType {
						case "bool":
							//fmt.Println("单字节:", value, item.MultiByte, item.BoolBits)
							if item.MultiByte == 1 {
								binArr := tools.ByteToBitArray(buffer[item.Start])
								value = float64(binArr[item.BoolBits])
							} else {
								value = float64(buffer[item.Start])
							}
						case "uint8":
							v := buffer[item.Start]
							value = float64(v)
						case "int8":
							v := buffer[item.Start]
							value = float64(int8(v))
						case "uint16":
							rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 2)
							v := tools.BytesToUint16(rawBytes, item.Endian)
							value = float64(v)
						case "int16":
							rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 2)
							v := tools.BytesToUint16(rawBytes, item.Endian)
							value = float64(int16(v))
						case "uint32":
							rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 4)
							v := tools.BytesToUint32(rawBytes, item.Endian)
							value = float64(v)
						case "int32":
							rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 4)
							v := tools.BytesToUint32(rawBytes, item.Endian)
							value = float64(int32(v))
						case "uint64":
							rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 8)
							v := tools.BytesToUint64(rawBytes, item.Endian)
							value = float64(v)
						case "int64":
							rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 8)
							v := tools.BytesToUint64(rawBytes, item.Endian)
							value = float64(int64(v))
						case "float32":
							v := tools.BytesToFloat32(buffer[item.Start:item.End+1], item.Endian)
							value = float64(v)
						case "float64":
							v := tools.BytesToFloat64(buffer[item.Start:item.End+1], item.Endian)
							value = v
						}
						data = append(data, []string{
							item.Addr,
							item.FieldName,
							item.SpecType,
							tools.ByteArrayToHexStr(buffer[item.Start : item.End+1]),
							fmt.Sprintf("%v", tools.FormatFloat(value)),
							fmt.Sprintf("%v", item.MultiByte),
							fmt.Sprintf("%v", item.Start),
							fmt.Sprintf("%v", item.End),
							fmt.Sprintf("%v", item.BoolBits),
						})

						// 变量保存到 redis 队列
						queueData := QueueValue{}
						queueData.SpecType = item.SpecType
						queueData.Addr = item.Addr
						queueData.FieldName = item.FieldName
						queueData.Value = value
						queueData.CreateTime = &now
						messageList = append(messageList, &queueData)
					}
				}

				if len(messageList) > 0 {
					go VariablesMap.PushMessageToClients(messageList)
				}
				/*--- 变量解析结束 ---*/
				if len(data) > 0 {
					table := tablewriter.NewWriter(os.Stdout)
					table.SetHeader([]string{"地址", "变量名", "类型", "原始数据", "解析值", "是否多字节", "START", "END", "BIT"})
					for _, v := range data {
						table.Append(v)
					}
					table.Render()
				}

				frameMatched = true // 匹配到合适帧格式, 终止循环
				if frame.RelatedFrameID != 0 {
					go func(relatedFrameID int) {
						err = RelationFrameTrigger(relatedFrameID)
						if err != nil {
							j_log.LogError([]interface{}{fmt.Sprintf("%s 触发关联帧失败; %s", ppe.Name, err.Error())})
						}
					}(frame.RelatedFrameID)
				}
				break loop1
			}
			if frameMatched == false {
				j_log.LogInfo([]interface{}{"???未知的数据包"})
			}
		}
	}
}

func rs485WatchDog(ppe *PPE, port io.ReadWriteCloser) {
	for {
		if ppe.RunningState == 3 {
			if port != nil {
				_ = port.Close()
			}
			return
		}
		time.Sleep(500 * time.Millisecond)
	}
}
